﻿/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File:	StringBuilderExtNumeric.cs
// Date:	9th March 2010
// Author:	Gavin Pugh
// Details:	Extension methods for the 'StringBuilder' standard .NET class, to allow garbage-free concatenation of
//			a selection of simple numeric types.  
//
// Copyright (c) Gavin Pugh 2010 - Released under the zlib license: http://www.opensource.org/licenses/zlib-license.php
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Meteor
{
	public static partial class StringBuilderExtensions
	{
		// These digits are here in a static array to support hex with simple, easily-understandable code. 
		// Since A-Z don't sit next to 0-9 in the ascii table.
		private static readonly char[] ms_digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

		private static readonly uint ms_default_decimal_places = 5; //< Matches standard .NET formatting dp's
		private static readonly char ms_default_pad_char = '0';

		//! Convert a given unsigned integer value to a string and concatenate onto the stringbuilder. Any base value allowed.
		public static StringBuilder Concat(this StringBuilder string_builder, uint uint_val, uint pad_amount, char pad_char, uint base_val)
		{
			Debug.Assert(pad_amount >= 0);
			Debug.Assert(base_val > 0 && base_val <= 16);

			// Calculate length of integer when written out
			uint length = 0;
			uint length_calc = uint_val;

			do
			{
				length_calc /= base_val;
				length++;
			}
			while (length_calc > 0);

			// Pad out space for writing.
			string_builder.Append(pad_char, (int)Math.Max(pad_amount, length));

			int strpos = string_builder.Length;

			// We're writing backwards, one character at a time.
			while (length > 0)
			{
				strpos--;

				// Lookup from static char array, to cover hex values too
				string_builder[strpos] = ms_digits[uint_val % base_val];

				uint_val /= base_val;
				length--;
			}

			return string_builder;
		}

		//! Convert a given unsigned integer value to a string and concatenate onto the stringbuilder. Assume no padding and base ten.
		public static StringBuilder Concat(this StringBuilder string_builder, uint uint_val)
		{
			string_builder.Concat(uint_val, 0, ms_default_pad_char, 10);
			return string_builder;
		}

		//! Convert a given unsigned integer value to a string and concatenate onto the stringbuilder. Assume base ten.
		public static StringBuilder Concat(this StringBuilder string_builder, uint uint_val, uint pad_amount)
		{
			string_builder.Concat(uint_val, pad_amount, ms_default_pad_char, 10);
			return string_builder;
		}

		//! Convert a given unsigned integer value to a string and concatenate onto the stringbuilder. Assume base ten.
		public static StringBuilder Concat(this StringBuilder string_builder, uint uint_val, uint pad_amount, char pad_char)
		{
			string_builder.Concat(uint_val, pad_amount, pad_char, 10);
			return string_builder;
		}

		//! Convert a given signed integer value to a string and concatenate onto the stringbuilder. Any base value allowed.
		public static StringBuilder Concat(this StringBuilder string_builder, int int_val, uint pad_amount, char pad_char, uint base_val)
		{
			Debug.Assert(pad_amount >= 0);
			Debug.Assert(base_val > 0 && base_val <= 16);

			// Deal with negative numbers
			if (int_val < 0)
			{
				string_builder.Append('-');
				uint uint_val = uint.MaxValue - ((uint)int_val) + 1; //< This is to deal with Int32.MinValue
				string_builder.Concat(uint_val, pad_amount, pad_char, base_val);
			}
			else
			{
				string_builder.Concat((uint)int_val, pad_amount, pad_char, base_val);
			}

			return string_builder;
		}

		//! Convert a given signed integer value to a string and concatenate onto the stringbuilder. Assume no padding and base ten.
		public static StringBuilder Concat(this StringBuilder string_builder, int int_val)
		{
			string_builder.Concat(int_val, 0, ms_default_pad_char, 10);
			return string_builder;
		}

		//! Convert a given signed integer value to a string and concatenate onto the stringbuilder. Assume base ten.
		public static StringBuilder Concat(this StringBuilder string_builder, int int_val, uint pad_amount)
		{
			string_builder.Concat(int_val, pad_amount, ms_default_pad_char, 10);
			return string_builder;
		}

		//! Convert a given signed integer value to a string and concatenate onto the stringbuilder. Assume base ten.
		public static StringBuilder Concat(this StringBuilder string_builder, int int_val, uint pad_amount, char pad_char)
		{
			string_builder.Concat(int_val, pad_amount, pad_char, 10);
			return string_builder;
		}

		//! Convert a given float value to a string and concatenate onto the stringbuilder
		public static StringBuilder Concat(this StringBuilder string_builder, float float_val, uint decimal_places, uint pad_amount, char pad_char)
		{
			Debug.Assert(pad_amount >= 0);

			if (decimal_places == 0)
			{
				// No decimal places, just round up and print it as an int

				// Agh, Math.Floor() just works on doubles/decimals. Don't want to cast! Let's do this the old-fashioned way.
				int int_val;
				if (float_val >= 0.0f)
				{
					// Round up
					int_val = (int)(float_val + 0.5f);
				}
				else
				{
					// Round down for negative numbers
					int_val = (int)(float_val - 0.5f);
				}

				string_builder.Concat(int_val, pad_amount, pad_char, 10);
			}
			else
			{
				int int_part = (int)float_val;

				// First part is easy, just cast to an integer
				string_builder.Concat(int_part, pad_amount, pad_char, 10);

				// Decimal point
				string_builder.Append('.');

				// Work out remainder we need to print after the d.p.
				float remainder = Math.Abs(float_val - int_part);
				uint places = decimal_places;
				uint factor = 1;

				// Multiply up to become an int that we can print
				do
				{
					remainder *= 10;
					factor *= 10;
					decimal_places--;
				}
				while (decimal_places > 0);

				// Round up. It's guaranteed to be a positive number, so no extra work required here.
				//remainder += 0.5f;
				do
				{
					if (remainder / (float)factor < 0.1f)
						string_builder.Append('0', 1);
					factor /= 10;
				}
				while (factor > 1);
				// All done, print that as an int!
				string_builder.Concat((uint)remainder, 0, '0', 10);
			}
			return string_builder;
		}

		//! Convert a given float value to a string and concatenate onto the stringbuilder. Assumes five decimal places, and no padding.
		public static StringBuilder Concat(this StringBuilder string_builder, float float_val)
		{
			string_builder.Concat(float_val, ms_default_decimal_places, 0, ms_default_pad_char);
			return string_builder;
		}

		//! Convert a given float value to a string and concatenate onto the stringbuilder. Assumes no padding.
		public static StringBuilder Concat(this StringBuilder string_builder, float float_val, uint decimal_places)
		{
			string_builder.Concat(float_val, decimal_places, 0, ms_default_pad_char);
			return string_builder;
		}

		//! Convert a given float value to a string and concatenate onto the stringbuilder.
		public static StringBuilder Concat(this StringBuilder string_builder, float float_val, uint decimal_places, uint pad_amount)
		{
			string_builder.Concat(float_val, decimal_places, pad_amount, ms_default_pad_char);
			return string_builder;
		}
	}
}